package com.project.util.wx;


import com.alibaba.fastjson.JSONObject;
import com.framework.core.http.HttpRequest;
import com.framework.core.http.HttpResponse;
import com.framework.core.http.HttpUtil;
import com.project.common.exception.ServiceException;
import com.project.pojo.dto.OrderPayJsapiDTO;
import com.project.pojo.dto.WxPayH5CreateOrderDTO;
import com.project.pojo.dto.WxPayH5CreateOrderResultDTO;
import com.project.pojo.dto.WxPayJSAPICreateOrderDTO;
import lombok.extern.slf4j.Slf4j;
import okhttp3.HttpUrl;
import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.beans.factory.annotation.Value;

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.cert.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.text.SimpleDateFormat;
import java.util.Base64;
import java.util.Calendar;
import java.util.Date;

/**
 * @Description: 微信支付帮助类
 * @Author: zhuchi
 * @CreateDate: 2020/12/3 11:40
 * @Version: 1.0
 */
@Slf4j
public class WxPaySignUtil {

    /**
     * 商户号
     */
    private static String MCH_ID = "1604306454";

    /**
     * 公众号APPID
     */
    public static String APP_ID = "wx47c78c8874bf0ecb";

    /**
     * 公众号APP_SECRET
     */
    public static String APP_SECRET = "cf275655fe6a09508c970a618eca90d2";

    /**
     * 证书序列号
     */
    private static String SERIAL_NO = "3304914BDEFC90A394574EA077612A2B89D91309";

    /**
     * API秘钥
     */
    public static String API_KEY = "W0z13reWApZWlfjSP7F7CjH8FehiJRZo";

    /**
     * API_V3秘钥
     */
    public static String API_V3_KEY = "W0z13reWApZWlfjSP7F7CjH8FehiJRZo";

    /**
     * 商品支付成功异步通知地址
     */
    private static String PRODUCT_NOTIFY_URL = "https://www.xiangjiapl.com/elephant-family/wx/payNotify";

    /**
     * 回收员充值支付成功异步通知地址
     */
    private static String RECOVERY_NOTIFY_URL = "https://www.xiangjiapl.com/elephant-family/wx/recoveryPayNotify";

    /**
     * 私钥文件路径（测试）
     */
//    private static String PRIVATE_KEY_PATH = "D:\\wxCert\\xiangjia\\apiclient_key.pem";

    /**
     * 私钥文件路径（正式）
     */
    private static String PRIVATE_KEY_PATH = "/opt/wxCert/xiangjia/apiclient_key.pem";

    /**
     * 证书文件路径（测试）
     */
//    private static String CERT_PATH = "D:\\wxCert\\xiangjia\\apiclient_cert.p12";

    /**
     * 证书文件路径（正式）
     */
    private static String CERT_PATH = "/opt/wxCert/xiangjia/apiclient_cert.p12";

    /**
     * 生成签名
     * @param method
     * @param url
     * @param body
     * @return
     */
    public static String getToken(String method, HttpUrl url, String body) {
        try {
            String nonceStr = RandomStringUtils.randomAlphanumeric(32);
            long timestamp = System.currentTimeMillis() / 1000;
            String message = buildMessage(method, url, timestamp, nonceStr, body);
            String signature = sign(message.getBytes("utf-8"));

            return "mchid=\"" + MCH_ID + "\","
                    + "nonce_str=\"" + nonceStr + "\","
                    + "timestamp=\"" + timestamp + "\","
                    + "serial_no=\"" + SERIAL_NO + "\","
                    + "signature=\"" + signature + "\"";
        }catch (Exception e){
            throw new ServiceException("生成签名发生错误");
        }
    }

    /**
     * 使用SHA256签名
     * @param message
     * @return
     */
    public static String sign(byte[] message) {
        try {
            Signature sign = Signature.getInstance("SHA256withRSA");
            sign.initSign(getPrivateKey(PRIVATE_KEY_PATH));
            sign.update(message);

            String signStr = Base64.getEncoder().encodeToString(sign.sign());
            return signStr;
        }catch (Exception e){
            throw new ServiceException("使用SHA256签名时发生错误");
        }
    }

    /**
     * 组装待签名字符串
     * @param method
     * @param url
     * @param timestamp
     * @param nonceStr
     * @param body
     * @return
     */
    public static String buildMessage(String method, HttpUrl url, long timestamp, String nonceStr, String body) {
        String canonicalUrl = url.encodedPath();
        if (url.encodedQuery() != null) {
            canonicalUrl += "?" + url.encodedQuery();
        }

        return method + "\n"
                + canonicalUrl + "\n"
                + timestamp + "\n"
                + nonceStr + "\n"
                + body + "\n";
    }

    /**
     * JSAPI调起支付签名
     * @param appId
     * @param appId
     * @param nonceStr
     * @param packageStr
     * @return
     */
    public static  String getJSAPIPaySign(String appId, Long timeStamp, String nonceStr, String packageStr){
        try {
            String signParam =  appId + "\n"
                    + timeStamp + "\n"
                    + nonceStr + "\n"
                    + packageStr + "\n";

            String signature = sign(signParam.getBytes("utf-8"));
            return signature;
        }catch (Exception e){
            throw new ServiceException("JSAPI调起支付签名错误");
        }

    }

    /**
     * 获取私钥。
     *
     * @param filename 私钥文件路径  (required)
     * @return 私钥对象
     */
    public static PrivateKey getPrivateKey(String filename){
        try {
            String content = new String(Files.readAllBytes(Paths.get(filename)), "utf-8");
            String privateKey = content.replace("-----BEGIN PRIVATE KEY-----", "")
                    .replace("-----END PRIVATE KEY-----", "")
                    .replaceAll("\\s+", "");

            KeyFactory kf = KeyFactory.getInstance("RSA");
            return kf.generatePrivate(
                    new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey)));
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("当前Java环境不支持RSA", e);
        } catch (InvalidKeySpecException e) {
            throw new RuntimeException("无效的密钥格式");
        } catch (IOException e) {
            throw new RuntimeException("读取私钥失败");
        }
    }

    /**
     * 获取证书。
     *
     * @param filename 证书文件路径  (required)
     * @return X509证书
     */
    public static X509Certificate getCertificate(String filename) throws IOException {
        InputStream fis = new FileInputStream(filename);
        BufferedInputStream bis = new BufferedInputStream(fis);

        try {
            CertificateFactory cf = CertificateFactory.getInstance("X509");
            X509Certificate cert = (X509Certificate) cf.generateCertificate(bis);
            cert.checkValidity();
            return cert;
        } catch (CertificateExpiredException e) {
            throw new RuntimeException("证书已过期", e);
        } catch (CertificateNotYetValidException e) {
            throw new RuntimeException("证书尚未生效", e);
        } catch (CertificateException e) {
            throw new RuntimeException("无效的证书文件", e);
        } finally {
            bis.close();
        }
    }

    /**
     * 微信H5支付发起支付订单
     * @return 返回支付链接
     */
    public static WxPayH5CreateOrderResultDTO wxPayH5CreateOrder(WxPayH5CreateOrderDTO wxPayH5CreateOrderDTO){
        SimpleDateFormat dateFormat1 = new SimpleDateFormat("yyyy-MM-dd");
        SimpleDateFormat dateFormat2 = new SimpleDateFormat("HH:mm:ss");

        String date = dateFormat1.format(wxPayH5CreateOrderDTO.getTimeExpire());
        String time = dateFormat2.format(wxPayH5CreateOrderDTO.getTimeExpire());

        //接口地址
        String url = "https://api.mch.weixin.qq.com/v3/pay/transactions/h5";
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("appid", APP_ID);                                        //公众号ID
        jsonObject.put("mchid", MCH_ID);                                        //直连商户号
        jsonObject.put("description", wxPayH5CreateOrderDTO.getDescription());  //商品描述
        jsonObject.put("out_trade_no", wxPayH5CreateOrderDTO.getOutTradeNo());  //商户订单号
        jsonObject.put("time_expire", date + "T" + time + "+08:00");            //交易结束时间
        jsonObject.put("attach", "自定义数据说明");                              //附加数据
        if (wxPayH5CreateOrderDTO.getPaySysType() == 1){
            jsonObject.put("notify_url", PRODUCT_NOTIFY_URL);                   //商品支付成功异步通知地址
        }else if (wxPayH5CreateOrderDTO.getPaySysType() == 2){
            jsonObject.put("notify_url", RECOVERY_NOTIFY_URL);                  //回收员充值支付成功异步通知地址
        }
        jsonObject.put("goods_tag", "WXG");                                     //订单优惠标记

        JSONObject amount = new JSONObject();                                   //订单金额信息
        amount.put("total", wxPayH5CreateOrderDTO.getTotal());                  //总金额
        amount.put("currency", "CNY");                                          //货币类型
        jsonObject.put("amount", amount);

//        JSONObject detail = new JSONObject();                                   //优惠功能
//        jsonObject.put("detail", detail);

        JSONObject scene_info = new JSONObject();                               //支付场景描述
        scene_info.put("payer_client_ip", wxPayH5CreateOrderDTO.getPayerClientIp());    //用户终端IP

        JSONObject h5_info = new JSONObject();                                  //H5场景信息
        h5_info.put("type", "Wap");                                             //场景类型	（iOS, Android, Wap）
        h5_info.put("app_name", "象家");                                        //应用名称
        h5_info.put("app_url", "https://www.xiangjiapl.com/");                  //网站URL
        h5_info.put("bundle_id", "");                                           //iOS平台BundleID
        h5_info.put("package_name", "");                                        //Android平台PackageName

        scene_info.put("h5_info", h5_info);

        jsonObject.put("scene_info", scene_info);

        String body = jsonObject.toJSONString();

        HttpRequest post = HttpUtil.createPost(url);

        HttpUrl httpurl = HttpUrl.parse(url);
        //生成签名
        String Authorization = getToken("POST", httpurl, body);
        post.header("Authorization", "WECHATPAY2-SHA256-RSA2048 " + Authorization);

        post.charset("UTF-8");
        log.info("H5支付下单入参:{}", body);
        HttpResponse execute = post.body(body, "application/json").execute();
        String bodyData = execute.body();
        System.out.println(bodyData);
        log.info("H5支付下单结果:{}", bodyData);

        JSONObject createOrder = JSONObject.parseObject(bodyData);

        WxPayH5CreateOrderResultDTO wxPayH5CreateOrderResultDTO = new WxPayH5CreateOrderResultDTO();
        if (createOrder.containsKey("h5_url")){
            wxPayH5CreateOrderResultDTO.setCode("200");
            wxPayH5CreateOrderResultDTO.setMessage("H5支付下单成功");
            wxPayH5CreateOrderResultDTO.setH5Url(createOrder.getString("h5_url"));
        }else {
            wxPayH5CreateOrderResultDTO.setCode("500");
            wxPayH5CreateOrderResultDTO.setMessage(createOrder.getString("code") + " | " + createOrder.getString("message"));
        }

        return wxPayH5CreateOrderResultDTO;
    }

    /**
     * 微信JSAPI支付发起支付订单
     * @return 返回支付链接
     */
    public static OrderPayJsapiDTO wxPayJSAPICreateOrder(WxPayJSAPICreateOrderDTO wxPayJSAPICreateOrderDTO){
        SimpleDateFormat dateFormat1 = new SimpleDateFormat("yyyy-MM-dd");
        SimpleDateFormat dateFormat2 = new SimpleDateFormat("HH:mm:ss");

        String date = dateFormat1.format(wxPayJSAPICreateOrderDTO.getTimeExpire());
        String time = dateFormat2.format(wxPayJSAPICreateOrderDTO.getTimeExpire());

        //接口地址
        String url = "https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi";
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("mchid", MCH_ID);                                            //直连商户号
        jsonObject.put("appid", APP_ID);                                            //公众号ID
        jsonObject.put("description", wxPayJSAPICreateOrderDTO.getDescription());   //商品描述
        jsonObject.put("out_trade_no", wxPayJSAPICreateOrderDTO.getOutTradeNo());   //商户订单号
        jsonObject.put("time_expire", date + "T" + time + "+08:00");                //交易结束时间
        jsonObject.put("attach", "自定义数据说明");                                  //附加数据
        if (wxPayJSAPICreateOrderDTO.getPaySysType() == 1){
            jsonObject.put("notify_url", PRODUCT_NOTIFY_URL);                       //商品支付成功异步通知地址
        }else if (wxPayJSAPICreateOrderDTO.getPaySysType() == 2){
            jsonObject.put("notify_url", RECOVERY_NOTIFY_URL);                      //回收员充值支付成功异步通知地址
        }
        jsonObject.put("goods_tag", "WXG");                                         //订单优惠标记

        JSONObject amount = new JSONObject();                                       //订单金额信息
        amount.put("total", wxPayJSAPICreateOrderDTO.getTotal());                   //总金额
        amount.put("currency", "CNY");                                              //货币类型
        jsonObject.put("amount", amount);

        JSONObject payer = new JSONObject();                                        //优惠功能
        payer.put("openid", wxPayJSAPICreateOrderDTO.getOpenId());                  //
        jsonObject.put("payer", payer);

        JSONObject scene_info = new JSONObject();                                   //支付场景描述
        scene_info.put("payer_client_ip", wxPayJSAPICreateOrderDTO.getPayerClientIp()); //用户终端IP

        jsonObject.put("scene_info", scene_info);

        String body = jsonObject.toJSONString();

        HttpRequest post = HttpUtil.createPost(url);

        HttpUrl httpurl = HttpUrl.parse(url);
        log.info("微信JSAPI支付发起支付订单参数信息：{}", wxPayJSAPICreateOrderDTO);
        //生成签名
        String Authorization = getToken("POST", httpurl, body);
        post.header("Authorization", "WECHATPAY2-SHA256-RSA2048 " + Authorization);

        post.charset("UTF-8");
        log.info("JSAPI支付下单入参:{}", body);
        HttpResponse execute = post.body(body, "application/json").execute();
        String bodyData = execute.body();
        System.out.println(bodyData);
        log.info("JSAPI支付下单结果:{}", bodyData);

        JSONObject createOrder = JSONObject.parseObject(bodyData);

        OrderPayJsapiDTO orderPayJsapiDTO = new OrderPayJsapiDTO();
        if (createOrder.containsKey("prepay_id")){
            orderPayJsapiDTO.setMessage("JSAPI支付下单成功");
            orderPayJsapiDTO.setCode("200");

            String nonceStr = RandomStringUtils.randomAlphanumeric(32);
            long timestamp = System.currentTimeMillis() / 1000;
            String packageStr = "prepay_id=" + createOrder.getString("prepay_id");

            String paySign = getJSAPIPaySign(WxPaySignUtil.APP_ID, timestamp, nonceStr, packageStr);

            orderPayJsapiDTO.setAppId(WxPaySignUtil.APP_ID);
            orderPayJsapiDTO.setTimeStamp(timestamp+"");
            orderPayJsapiDTO.setNonceStr(nonceStr);
            orderPayJsapiDTO.setPackageStr(packageStr);
            orderPayJsapiDTO.setSignType("RSA");
            orderPayJsapiDTO.setPaySign(paySign);
            System.out.println(orderPayJsapiDTO);
        }else {
            orderPayJsapiDTO.setCode("500");
            orderPayJsapiDTO.setMessage(createOrder.getString("code") + " | " + createOrder.getString("message"));
        }

        return orderPayJsapiDTO;
    }

    public static void main(String[] args) {
        Date nowTime = new Date();
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(nowTime);
        //当前时间加2小时
        calendar.add(Calendar.HOUR_OF_DAY, 2);
        //微秒设置为0,防止mysql自动加一秒
        //MySQL数据库对于毫秒大于500的数据进行进位
        calendar.set(Calendar.MILLISECOND, 0);

        //最晚支付时间
        Date deadlineTime = calendar.getTime();
        WxPayH5CreateOrderDTO wxPayH5CreateOrderDTO = new WxPayH5CreateOrderDTO();
        wxPayH5CreateOrderDTO.setTimeExpire(deadlineTime);
        wxPayH5CreateOrderDTO.setPayerClientIp("221.216.122.176");
        wxPayH5CreateOrderDTO.setTotal(50);
        wxPayH5CreateOrderDTO.setOutTradeNo(RandomStringUtils.randomAlphanumeric(32));
        wxPayH5CreateOrderDTO.setDescription("商品支付");
//        WxPayH5CreateOrderResultDTO wxPayH5CreateOrderResultDTO = wxPayH5CreateOrder(wxPayH5CreateOrderDTO);
        //System.out.println(wxPayH5CreateOrderResultDTO);

        WxPayJSAPICreateOrderDTO wxPayJSAPICreateOrderDTO = new WxPayJSAPICreateOrderDTO();
        wxPayJSAPICreateOrderDTO.setTimeExpire(deadlineTime);
        wxPayJSAPICreateOrderDTO.setPayerClientIp("221.216.122.176");
        wxPayJSAPICreateOrderDTO.setPaySysType(1);
        wxPayJSAPICreateOrderDTO.setTotal(50);
        wxPayJSAPICreateOrderDTO.setOutTradeNo(RandomStringUtils.randomAlphanumeric(32));
        wxPayJSAPICreateOrderDTO.setDescription("商品支付");
        wxPayJSAPICreateOrderDTO.setOpenId("otD7Vt0sP647EghWmYNZAlcFRcwQ");
        OrderPayJsapiDTO orderPayJsapiDTO = wxPayJSAPICreateOrder(wxPayJSAPICreateOrderDTO);
        System.out.println(orderPayJsapiDTO);
    }

}
